package cHTTPClient

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"gitee.com/csingo/cHelper"

	"gitee.com/csingo/cCommon"
	"gitee.com/csingo/cContext"
	"gitee.com/csingo/cLog"
)

type client struct {
	ctx    *gin.Context
	option *Option

	interval int
}

func (c *client) url() string {
	var host, uri, query string
	var builder strings.Builder
	var fields = make([]string, 0)

	host = strings.TrimSuffix(c.option.Host, "/")
	uri = strings.TrimPrefix(c.option.Uri, "/")

	for k, v := range c.option.Query {
		fields = append(fields, fmt.Sprintf("%s=%s", k, v))
	}
	query = strings.Join(fields, "&")

	builder.WriteString(host)
	if uri != "" {
		builder.WriteString("/")
		builder.WriteString(uri)
	}
	if strings.Contains(host, "?") || strings.Contains(uri, "?") {
		builder.WriteString("&")
	} else {
		builder.WriteString("?")
	}
	builder.WriteString(query)

	return builder.String()
}

func (c *client) send() (rsp *Response, err error) {
	var request *http.Request
	var response *http.Response

	var current int
	for current < c.option.Try || c.option.Try < 0 {
		if current > 0 {
			var msg string
			if err != nil {
				msg = err.Error()
			}
			cLog.WithContext(c.ctx, map[string]any{
				"source": "cHTTPClient.client.send",
				"retry":  current,
				"err":    msg,
			}).Error("外部请求重试")
		}

		c.sleep(current)

		// 创建客户端
		cli := &http.Client{
			CheckRedirect: c.option.RedirectHandler,
			Transport: &http.Transport{
				DialContext: (&net.Dialer{
					Timeout: c.option.ConnectTimeout * time.Second, // 设置连接超时
				}).DialContext,
			},
			Timeout: c.option.Timeout, // 设置请求超时，包括连接建立、重定向及读取响应主体的时间
		}

		// 创建请求
		var body io.Reader
		body, err = formatRequestBody(c.option.Data, c.option.Headers[HeaderContentType])
		if err != nil {
			return nil, err
		}
		request, err = http.NewRequest(string(c.option.Method), c.url(), body)
		if err != nil {
			return nil, err
		}

		// 设置请求头
		for k, v := range c.option.Headers {
			request.Header.Set(k, v)
		}

		// 设置cookies
		if len(c.option.Cookies) > 0 {
			for _, cookie := range c.option.Cookies {
				request.AddCookie(cookie)
			}
		}

		// 发起请求
		var retry bool
		response, err = cli.Do(request)
		if c.option.ResponseHandler != nil {
			retry, err = c.option.ResponseHandler(c.ctx, response, err)
			if !retry {
				break
			}
		}
		// if err == nil {
		//	var isRetry bool
		//	if c.option.ResponseHandler != nil {
		//		isRetry, err = c.option.ResponseHandler(c.ctx, response)
		//	}
		//
		//	if err == nil {
		//		break
		//	}
		//
		//	if !isRetry {
		//		break
		//	}
		// }

		current++
	}

	rsp, err = c.response(response)

	return
}

func (c *client) response(r *http.Response) (rsp *Response, err error) {
	if r == nil {
		rsp = &Response{
			Header:     make(http.Header),
			Status:     http.StatusText(http.StatusNoContent),
			StatusCode: http.StatusNoContent,
			Body:       make([]byte, 0),
		}
		return
	}

	defer r.Body.Close()

	if r.Header == nil {
		r.Header = make(http.Header)
	}

	rsp = &Response{
		Header:     r.Header,
		Cookies:    r.Cookies(),
		Status:     r.Status,
		StatusCode: r.StatusCode,
		Body:       nil,
	}

	if r.StatusCode != http.StatusOK {
		err = errors.New(r.Status)
		return
	}

	switch r.Header.Get("Content-Type") {
	case "text/event-stream":
		if c.option.Stream == nil {
			rsp.Body, err = io.ReadAll(r.Body)
			if err != nil {
				return
			}
			break
		}
		reader := bufio.NewReader(r.Body)
		for {
			var line []byte
			var content string

			line, err = reader.ReadBytes('\n')
			if err != nil {
				c.option.Stream <- err.Error()
				break
			}

			content = string(line)
			content = strings.TrimPrefix(content, "data:")
			content = strings.Trim(content, "")
			content = strings.Trim(content, "\n")

			if strings.TrimSpace(content) == cCommon.HTTPStreamEnd {
				break
			}
			c.option.Stream <- content
		}
		c.option.Stream <- cCommon.HTTPStreamEnd
	default:
		rsp.Body, err = io.ReadAll(r.Body)
		if err != nil {
			return
		}
		if c.option.Stream != nil {
			c.option.Stream <- string(rsp.Body)
		}
	}

	return
}

func (c *client) sleep(count int) {
	try := count - 1
	if try < 0 {
		try = 0
	}
	incr := try / c.option.IntervalTry * c.option.IntervalIncr
	if count > 0 {
		c.interval = incr + c.option.Interval
	}
	if c.interval > c.option.IntervalMax {
		c.interval = c.option.IntervalMax
	}
	if c.interval > 0 {
		time.Sleep(time.Duration(c.interval) * time.Second)
	}
}

func newClient(ctx *gin.Context, option *Option) (cli *client) {
	if option.Interval < 0 {
		option.Interval = 0
	}
	if option.IntervalIncr < 0 {
		option.IntervalIncr = 0
	}
	if option.IntervalMax < 0 {
		option.IntervalMax = 0
	}
	if option.IntervalTry < 1 {
		option.IntervalTry = 1
	}
	if option.Try == 0 {
		option.Try = 3
	}
	if option.Headers == nil {
		option.Headers = make(map[string]string)
	}
	if option.Method == "" {
		option.Method = MethodGET
	}
	option.Headers[cCommon.XIndex_TraceId] = cContext.GetTraceId(ctx)
	if _, ok := option.Headers[HeaderContentType]; !ok && option.Method != MethodGET {
		option.Headers[HeaderContentType] = HeaderContentTypeJson
	}
	if option.ResponseHandler == nil {
		option.ResponseHandler = defaultResponseHandler
	}

	cli = &client{
		ctx:      ctx,
		option:   option,
		interval: 0,
	}

	return
}

func defaultResponseHandler(ctx *gin.Context, response *http.Response, e error) (retry bool, err error) {
	if e != nil {
		return true, e
	}
	if response.StatusCode != http.StatusOK {
		return true, errors.New(response.Status)
	}
	return
}

func formatRequestBody(data any, contentType string) (body io.Reader, err error) {
	if cHelper.IsString(data) {
		body = strings.NewReader(data.(string))
		return
	}

	switch contentType {
	default:
		return
	case HeaderContentTypeJson:
		var buf bytes.Buffer
		encoder := json.NewEncoder(&buf)
		encoder.SetEscapeHTML(false)
		if err = encoder.Encode(data); err != nil {
			return
		}

		body = &buf
		return
	case HeaderContentTypeMultipart:
		body = data.(io.Reader)
		return
	}
}
